#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>
#include <QDebug>
#include <QSocketNotifier>
#include "dbus-monitor.h"
#include "dbus-monitor_p.h"



#define INDENT 4

DBusMonitorPrivate::DBusMonitorPrivate(DBusMonitor *p) : q_ptr(p) {
    this->moveToThread(p);
}


void DBusMonitorPrivate::quit() {
    q_ptr->quit();
}
bool DBusMonitor::create_and_init_connection() {
    return d_ptr->create_and_init_connection();
}

bool DBusMonitorPrivate::create_and_init_connection() {
    const char *typestr = nullptr;
    DBusError err = DBUS_ERROR_INIT;
    if(m_addr.isEmpty()) {
        switch (m_type) {
        case DBUS_BUS_SYSTEM:
            typestr = "system";
            break;
        case DBUS_BUS_SESSION:
            typestr = "session";
            break;
        default:
            fprintf(stderr, "Unspported bus type: %d\n", m_type);
            return false;
        }
        m_conn = dbus_bus_get_private(m_type, &err);
        if(m_conn == nullptr) {
            fprintf(stderr, "Failed to connect to %s bus: %s: %s\n",
                            typestr, err.name, err.message);

            dbus_error_free(&err);
            return false;
        }
    }else {
        std::string saddr = m_addr.toStdString();
        m_conn = dbus_connection_open_private(saddr.c_str(), &err);
        if(m_conn == nullptr) {
            fprintf(stderr, "Failed to open connection to bus: %s: %s\n",
                            err.name, err.message);
            dbus_error_free(&err);
            return false;
        } else if(!dbus_bus_register (m_conn, &err)) {
            fprintf(stderr, "Failed to register connection to bus: %s: %s\n",
                            err.name, err.message);
            dbus_error_free(&err);
            return false;
        }

    }

    dbus_connection_set_route_peer_messages (m_conn, TRUE);
    //dbus_connection_set_exit_on_disconnect(m_conn, FALSE);
    if (!dbus_connection_set_watch_functions(m_conn, add_watch, remove_watch,
                                              nullptr, this, nullptr)) {
         qWarning() << "dbus_connection_set_watch_functions() failed";
         return false;

     }
    return true;
}



DBusMonitor::DBusMonitor(DBusBusType type) : d_ptr(new DBusMonitorPrivate(this)) {
    d_ptr->m_type = type;
}

DBusMonitor::DBusMonitor(const QString &addr) :
    d_ptr(new DBusMonitorPrivate(this)){
    d_ptr->m_addr = addr;
}




DBusMonitor::~DBusMonitor() {
    delete d_ptr;
}



void DBusMonitorPrivate::handle_dbus_read(int fd) {
    DBusWatch *w = fddwm[fd];
    while (!dbus_watch_handle(w, DBUS_WATCH_READABLE)) {
            fprintf(stderr, "dbus_watch_handle needs more memory\n");
            q_ptr->sleep(1);
    }
    dbus_connection_ref(m_conn);
    while (dbus_connection_get_dispatch_status(m_conn) == DBUS_DISPATCH_DATA_REMAINS)
        dbus_connection_dispatch(m_conn);
    dbus_connection_unref(m_conn);
}
void DBusMonitorPrivate::handle_dbus_write(int fd) {
    qWarning("function:%s, line:%d, file:%s\n", __func__, __LINE__, __FILE__);
}
void DBusMonitorPrivate::handle_dbus_execption(int fd) {
     qWarning("function:%s, line:%d, file:%s\n", __func__, __LINE__, __FILE__);
    Q_EMIT q_ptr->connectionLost();
}


dbus_bool_t DBusMonitorPrivate::add_watch(DBusWatch *w, void *data) {
     QSocketNotifier *qs = nullptr;
    if (!dbus_watch_get_enabled(w))
        return TRUE;

    DBusMonitorPrivate *d = static_cast<DBusMonitorPrivate*>(data);

    int fd = dbus_watch_get_unix_fd(w);
    unsigned int flags = dbus_watch_get_flags(w);
    qs = new QSocketNotifier(fd, QSocketNotifier::Exception);
    qs->moveToThread(d->q_ptr);
    QObject::connect(qs, SIGNAL(activated(int)), d,
                     SLOT(handle_dbus_execption(int)));
    d->fddwm.insert(fd, w);
    d->fdqsm.insert(fd, qs);
    if(flags & DBUS_WATCH_READABLE) {
        qs = new QSocketNotifier(fd, QSocketNotifier::Read);
        qs->moveToThread(d->q_ptr);
        QObject::connect(qs, SIGNAL(activated(int)), d,
                         SLOT(handle_dbus_read(int)));
        d->fdqsm.insertMulti(fd, qs);
    };
    if(flags & DBUS_WATCH_WRITABLE) {
        qs = new QSocketNotifier(fd, QSocketNotifier::Write);
        qs->moveToThread(d->q_ptr);
        QObject::connect(qs, SIGNAL(activated(int)), d,
                         SLOT(handle_dbus_write(int)));
        d->fdqsm.insertMulti(fd, qs);
    }

    printf("added dbus watch fd=%d watch=%p, flags:%d\n", fd, w, flags);
    return TRUE;
}

void DBusMonitorPrivate::remove_watch(DBusWatch *w, void *data) {
    DBusMonitorPrivate *d = reinterpret_cast<DBusMonitorPrivate*>(data);
    int fd = dbus_watch_get_unix_fd(w);
    auto qss = d->fdqsm.values(fd);
    for(auto it=qss.begin(); it!=qss.end(); it++) {
        delete *it;
    }
    d->fdqsm.remove(fd);
    d->fddwm.remove(fd);
    printf("removed dbus watch watch=%p\n", w);
}





void DBusMonitor::run() {
    exec();
    if(d_ptr->m_conn) {
        dbus_connection_flush(d_ptr->m_conn);
        dbus_connection_close(d_ptr->m_conn);
        dbus_connection_unref(d_ptr->m_conn);
        d_ptr->m_conn = nullptr;
    }

}


bool DBusMonitor::add_rule(const QString &rule) {
    DBusError err = DBUS_ERROR_INIT;
    std::string srule = rule.toStdString();
    dbus_bus_add_match(d_ptr->m_conn, srule.c_str(), &err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr, "Failed to add rule: %s: %s\n", err.name, err.message);
        return false;
    }
    return true;
}

bool DBusMonitor::remove_rule(const QString &rule) {
    DBusError err = DBUS_ERROR_INIT;
    std::string srule = rule.toStdString();
    dbus_bus_remove_match(d_ptr->m_conn, srule.c_str(), &err);
    if(dbus_error_is_set(&err)) {
        fprintf(stderr, "Failed to remove rule: %s: %s\n", err.name, err.message);
        return false;
    }
    return true;

}


bool DBusMonitor::add_rules(const QStringList &rules) {
    DBusError error = DBUS_ERROR_INIT;
    DBusMessage *m;
    DBusMessage *r = nullptr;
    int i;
    dbus_uint32_t zero = 0;
    DBusMessageIter appender, array_appender;
    std::string srule;

    m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
                                      DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor");

    if (m == nullptr) {
        fprintf(stderr, "OOM while %s\n", "becoming a monitor");
        goto OUT;
    }

    dbus_message_iter_init_append (m, &appender);
    if (!dbus_message_iter_open_container (&appender, DBUS_TYPE_ARRAY, "s",
                                           &array_appender)) {
        fprintf(stderr, "OOM while %s\n", "opening string array");
        goto OUT;
    }

    for (i = 0; i < rules.size(); i++) {
        srule = QString("eavesdrop=true, %1").arg(rules[i]).toStdString();
        const char *rp = srule.c_str();
        if (!dbus_message_iter_append_basic (&array_appender, DBUS_TYPE_STRING,
                                             &rp)) {
            fprintf(stderr, "OOM while %s\n", "adding rule to array");
            goto OUT;
        }
    }

    if (!dbus_message_iter_close_container (&appender, &array_appender) ||
            !dbus_message_iter_append_basic (&appender, DBUS_TYPE_UINT32, &zero)) {

        fprintf(stderr, "OOM while %s\n", "finishing arguments");
        goto OUT;
    }

    r = dbus_connection_send_with_reply_and_block (d_ptr->m_conn, m, -1, &error);

    if (r == nullptr) {
        fprintf (stderr, "dbus-monitor: failed to call new-style monitoring: "
                         "%s: \"%s\".\n",
                 error.name, error.message);
        dbus_error_free (&error);
    } else {
        dbus_message_unref (r);
    }
    OUT:
    if(m) {
        dbus_message_unref (m);
    }
    return (r != nullptr);

}


bool DBusMonitor::add_handler(DBusHandleMessageFunction handler,
                              void *usr_data,
                              DBusFreeFunction fdf) {
    return dbus_connection_add_filter(d_ptr->m_conn, handler, usr_data, fdf);
}


void DBusMonitor::remove_handler(DBusHandleMessageFunction handler,
                                 void *usr_data) {

    dbus_connection_remove_filter(d_ptr->m_conn, handler, usr_data);
}

void DBusMonitor::stop() {
    QMetaObject::invokeMethod(d_ptr, "quit", Qt::QueuedConnection);
}


static const char * const msg_type2str[] = {
    "", //padding
    "method call", //DBUS_MESSAGE_TYPE_METHOD_CAL
    "methdo return", //DBUS_MESSAGE_TYPE_METHOD_RETURN
    "error", //DBUS_MESSAGE_TYPE_ERROR
    "signal" //DBUS_MESSAGE_TYPE_SIGNAL
};


DBusHandlerResult DBusMonitor::default_handler(DBusConnection *connection, DBusMessage *message, void *usr_data) {

    print_message(message);
    DBusMonitor *dbm = static_cast<DBusMonitor *>(usr_data);
    if (dbus_message_is_signal (message,
                                DBUS_INTERFACE_LOCAL,
                                "Disconnected")) {
        fprintf(stderr, "Dbus Disconnected!");
        if(dbm) dbm->connectionLost();

    }
    /* Monitors must not allow libdbus to reply to messages, so we eat
     * the message. See bug 1719.
     */
    return DBUS_HANDLER_RESULT_HANDLED;

}

void DBusMonitor::print_message(DBusMessage *message) {
    DBusMessageIter iter; //参数/返回值迭代器
    const char *sender;
    const char *destination;
    int message_type;
    struct timeval t;
    gettimeofday(&t, nullptr);
    message_type = dbus_message_get_type (message);
    sender = dbus_message_get_sender (message);
    destination = dbus_message_get_destination (message);
    printf("%s time=%ld.%06ld sender=%s -> destination=%s",
           msg_type2str[message_type],
           t.tv_sec, t.tv_usec,
           sender ?sender: "(null sender)",
           destination ?destination: "(null destination)");
    switch (message_type) {
    case DBUS_MESSAGE_TYPE_METHOD_CALL:
    case DBUS_MESSAGE_TYPE_SIGNAL:
        printf (" serial=%u path=%s; interface=%s; member=%s\n",
                dbus_message_get_serial (message),
                dbus_message_get_path (message),
                dbus_message_get_interface (message),
                dbus_message_get_member (message));
        break;
    case DBUS_MESSAGE_TYPE_METHOD_RETURN:
        printf (" serial=%u reply_serial=%u\n",
                dbus_message_get_serial (message),
                dbus_message_get_reply_serial (message));
        break;
    case DBUS_MESSAGE_TYPE_ERROR:
        printf (" error_name=%s reply_serial=%u\n",
                dbus_message_get_error_name (message),
                dbus_message_get_reply_serial (message));
        break;
    default:
        printf ("\n");
        break;
    }
    dbus_message_iter_init (message, &iter);
    walk_iter(&iter, 1);
    fflush (stdout);
}

void DBusMonitor::walk_iter(DBusMessageIter *iter, int depth) {
    while(dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INVALID) {
        print_iter(iter, depth);
        dbus_message_iter_next (iter);
    }
}

void DBusMonitor::print_iter(DBusMessageIter *iter, int depth) {
    char *vstr;
    dbus_int16_t vi16;
    dbus_uint16_t vu16;
    dbus_int32_t vi32;
    dbus_uint32_t vu32;
    dbus_int64_t vi64;
    dbus_uint64_t vu64;
    double vdf;
    unsigned char vuc;
    dbus_bool_t vbool;
    DBusMessageIter vsubiter;
    int vfd;
    int current_type;
    int type = dbus_message_iter_get_arg_type (iter);
    printf("%*s", depth*INDENT, "");
    switch (type) {
    case DBUS_TYPE_STRING:
        dbus_message_iter_get_basic (iter, &vstr);
        printf ("string \"%s\"\n", vstr);
        break;
    case DBUS_TYPE_SIGNATURE:
        dbus_message_iter_get_basic (iter, &vstr);
        printf ("signature \"%s\"\n", vstr);
        break;
    case DBUS_TYPE_OBJECT_PATH:
        dbus_message_iter_get_basic (iter, &vstr);
        printf ("object path \"%s\"\n", vstr);
        break;
    case DBUS_TYPE_INT16:
        dbus_message_iter_get_basic (iter, &vi16);
        printf ("int16 %d\n", vi16);
        break;
    case DBUS_TYPE_UINT16:
        dbus_message_iter_get_basic (iter, &vu16);
        printf ("uint16 %u\n", vu16);
        break;
    case DBUS_TYPE_INT32:
        dbus_message_iter_get_basic (iter, &vi32);
        printf ("int32 %d\n", vi32);
        break;
    case DBUS_TYPE_UINT32:
        dbus_message_iter_get_basic (iter, &vu32);
        printf ("uint32 %u\n", vu32);
        break;
    case DBUS_TYPE_INT64:
        dbus_message_iter_get_basic (iter, &vi64);
        printf ("int64 %ld\n", vi64);
        break;
    case DBUS_TYPE_UINT64:
        dbus_message_iter_get_basic (iter, &vu64);
        printf ("uint64 %lu\n", vu64);
        break;
    case DBUS_TYPE_DOUBLE:
        dbus_message_iter_get_basic (iter, &vdf);
        printf ("double %g\n", vdf);
        break;
    case DBUS_TYPE_BYTE:
        dbus_message_iter_get_basic (iter, &vuc);
        printf ("byte %d\n", vuc);
        break;
    case DBUS_TYPE_BOOLEAN:
        dbus_message_iter_get_basic (iter, &vbool);
        printf ("boolean %s\n", vbool ? "true" : "false");
        break;
    case DBUS_TYPE_VARIANT:
        //vsubiter此时代表variant包裹的一个数据(可以是任意类型)
        dbus_message_iter_recurse (iter, &vsubiter);
        printf ("variant\n");
        walk_iter(&vsubiter, depth+1);
        break;
    case DBUS_TYPE_ARRAY:
        //vsubiter此时代表arry元素(任意类型)的迭代器
        dbus_message_iter_recurse (iter, &vsubiter);
        //字节码数组特殊处理
        current_type = dbus_message_iter_get_arg_type (&vsubiter);
        if (current_type == DBUS_TYPE_BYTE) {
            print_ay(&vsubiter, depth);
            break;
        }
        printf("array [\n");
        walk_iter(&vsubiter, depth+1);
        printf("%*s]\n", depth*INDENT, "");
        break;
    case DBUS_TYPE_DICT_ENTRY:
        printf("dict entry(\n");
        //vsubiter此时为一个(键,值)对的迭代器
        dbus_message_iter_recurse (iter, &vsubiter);
        walk_iter(&vsubiter, depth+1);
        printf("%*s)\n", depth*INDENT, "");
        break;
    case DBUS_TYPE_STRUCT:
        printf("struct {\n");
        //vsbuiter:结构体元素迭代器
        dbus_message_iter_recurse (iter, &vsubiter);
        walk_iter(&vsubiter, depth+1);
        printf("%*s}\n", depth*INDENT, "");
        break;
    case DBUS_TYPE_UNIX_FD:
        dbus_message_iter_get_basic (iter, &vfd);
        print_fd (vfd, depth+1);
        /* dbus_message_iter_get_basic() duplicated the fd, we need to
               * close it after use. The original fd will be closed when the
               * DBusMessage is released.
               */
        close (vfd);
        break;

    default:
        printf (" (dbus-monitor too dumb to decipher arg type '%c')\n", type);
        break;
    }
}

void DBusMonitor::print_ay(DBusMessageIter *iter, int depth) {
    dbus_bool_t all_ascii = TRUE;
    const unsigned char *bytes;
    int len;
    int i;
    dbus_message_iter_get_fixed_array (iter, &bytes, &len);
    for (i = 0; i < len; i++){
        if ((bytes[i] < 32 || bytes[i] > 126) &&
                (i < len - 1 || bytes[i] != '\0')) {
            all_ascii = FALSE;
            break;
        }
    }
    if (all_ascii && len > 0 && bytes[len - 1] == '\0') {
        printf ("array of bytes \"%s\" + \\0\n", bytes);
    } else if (all_ascii) {
        unsigned char *copy = (unsigned char *)dbus_malloc (len + 1);
        if (copy == NULL) {
            fprintf(stderr, "OOM while %s\n", "copying bytestring");
            return;
        }
        memcpy (copy, bytes, len);
        copy[len] = '\0';
        printf ("array of bytes \"%s\"\n", copy);
        dbus_free (copy);
    }else {
        print_hex (bytes, len, depth);
    }

}

void DBusMonitor::print_hex(const unsigned char *bytes, unsigned len, int depth) {
    unsigned int i, columns;
    printf ("array of bytes [\n");
    printf("%*s", (depth + 1)*4, "");
    /* Each byte takes 3 cells (two hexits, and a space), except the last one. */
    columns = (80 - ((depth + 1) * INDENT)) / 3;
    if (columns < 8) columns = 8;
    i = 0;
    while (i < len) {
        printf ("%02x", bytes[i]);
        i++;
        if (i != len) {
            if (i % columns == 0) {
                printf ("\n");
                printf("%*s", (depth + 1)*4, "");
            } else {
                printf (" ");
            }
        }
    }
    printf ("\n%*s]\n", depth*4, "");
}


void DBusMonitor::print_fd (int fd, int depth) {
    int ret;
    struct stat statbuf = {0,};
    printf ("file descriptor %d\n", fd);
    if (fd == -1) return;
    ret = fstat (fd, &statbuf);
    if (ret == -1) return;
    printf("%*s", (depth+1)*4, "");
    printf ("inode: %d\n", (int) statbuf.st_ino);
    printf("%*s", (depth+1)*4, "");
    printf ("type: ");
    switch (statbuf.st_mode & S_IFMT) {
    case S_IFREG: printf ("file\n"); break;
    case S_IFDIR: printf ("directory\n"); break;
    case S_IFCHR: printf ("char\n"); break;
    case S_IFBLK: printf ("block\n"); break;
    case S_IFIFO: printf ("fifo\n"); break;
    case S_IFLNK: printf ("link\n"); break;
    case S_IFSOCK: printf ("socket\n"); break;
    default: printf("unknown?\n");  break;
    }

}



